using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.Serialization;

namespace Legend.Web.QueryString.Tests.AttributeParameterAccessorTests
{
    [TestFixture]
    public class AttributedParameterAccessorTestsBase
    {
        
        [Test]
        public void SimpleTypePropertyIsAccessed()
        {
            var o = new SimpleTypePropertyAttributed();
            o.MyProperty = 1;

            IDictionary<string, string> result = this.CreateTestableAccessor(o).GetAllParametersAndValues();

            AssertThatFirsKeyValuePairEquals("MyProperty", "1", result);
        }

        [Test]
        public void SimpleTypeFieldIsAccessed()
        {
            var o = new SimpleTypeFieldAttributed();
            o.MyField = 1;

            IDictionary<string, string> result = this.CreateTestableAccessor(o).GetAllParametersAndValues();

            AssertThatFirsKeyValuePairEquals("MyField", "1", result);
        }

        [Test]
        public void ComplexTypePropertyIsAccessed()
        {
            var o = new ComplexTypePropertyAttributed();
            o.Foo = new Foo();
            o.Foo.Bar = 1;
            o.Foo.Baz = "test";

            IDictionary<string, string> result = this.CreateTestableAccessor(o).GetAllParametersAndValues();

            AssertThatFirsKeyValuePairEquals("Foo", "1:test", result);
        }

        [Test]
        public void CustomTypeConverterIsUsedWhenSpecifiedOnProperty()
        {
            var o = new PropertyTypeConverterSpecified();
            o.MyProperty = true;

            IDictionary<string, string> result = this.CreateTestableAccessor(o).GetAllParametersAndValues();
            AssertThatFirsKeyValuePairEquals("MyProperty", "y", result);
        }

        [Test]
        public void CustomTypeConverterIsUsedWhenSpecifiedOnField()
        {
            var o = new FieldTypeConverterSpecified();
            o.MyField = true;

            IDictionary<string, string> result = this.CreateTestableAccessor(o).GetAllParametersAndValues();
            AssertThatFirsKeyValuePairEquals("MyField", "y", result);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void ConstructorCalledWithNullObjectThrowsException()
        {
            var o = new AttributedParameterAccessor(null);
        }

        [Test]
        public void GetAllParametersAndValuesOnlyGetsAttributedMembersValues()
        {
            var o = new NonAttributedType();
            var accessor = this.CreateTestableAccessor(o);

            var result = accessor.GetAllParametersAndValues();

            Assert.AreEqual(0, result.Count);
        }

        private void AssertThatFirsKeyValuePairEquals(string key, string value, IDictionary<string, string> values)
        {
            var first = values.First();
            Assert.AreEqual(key, first.Key);
            Assert.AreEqual(value, first.Value);
        }
        

        
        [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void ApplyParameterValueWhenNoMemberHasBeenAttributedWithTheParameterNameThrowsException()
        {
            var o = new SimpleTypePropertyAttributed();
            var accessor = this.CreateTestableAccessor(o);

            accessor.ApplyParameterValue("WrongParameterName", "1");
        }

        [Test]
        public void ApplyParameterValueToSimpleTypePropertyAppliesValue()
        {
            var o = new SimpleTypePropertyAttributed();
            var accessor = this.CreateTestableAccessor(o);

            accessor.ApplyParameterValue("MyProperty", "1");

            Assert.AreEqual(1, o.MyProperty);
        }

        [Test]
        public void ApplyParameterValueToSimpleTypeFieldAppliesValue()
        {
            var o = new SimpleTypeFieldAttributed();
            var accessor = this.CreateTestableAccessor(o);

            accessor.ApplyParameterValue("MyField", "1");

            Assert.AreEqual(1, o.MyField);
        }

        [Test]
        public void ApplyParameterValueToComplexTypePropertyAppliesValue()
        {
            var o = new ComplexTypePropertyAttributed();
            var accessor = this.CreateTestableAccessor(o);

            accessor.ApplyParameterValue("Foo", "1:a");

            Assert.AreEqual(1, o.Foo.Bar);
            Assert.AreEqual("a", o.Foo.Baz);
        }

        [Test]
        public void ApplyParameterValueToPropertyThatHasCustomTypeConverterSpecifiedAppliesValue()
        {
            var o = new PropertyTypeConverterSpecified();
            var accessor = this.CreateTestableAccessor(o);

            accessor.ApplyParameterValue("MyProperty", "y");

            Assert.IsTrue(o.MyProperty);
        }

        [Test]
        public void ApplyParameterValueToFieldThatHasCustomTypeConverterSpecifiedAppliesValue()
        {
            var o = new FieldTypeConverterSpecified();
            var accessor = this.CreateTestableAccessor(o);
            accessor.ApplyParameterValue("MyField", "y");

            Assert.IsTrue(o.MyField);
        }

        [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void ApplyingStringValueThatCanNotBeConvertedThrowsException()
        {
            var o = new SimpleTypeFieldAttributed();
            var accessor = this.CreateTestableAccessor(o);

            accessor.ApplyParameterValue("MyField", "this is not an integer");
        }
        

        private AttributedParameterAccessor CreateTestableAccessor(object @object)
        {
            return new AttributedParameterAccessor(@object);
        }

        [DataContract]
        public class SimpleTypePropertyAttributed
        {
            [DataMember]
            public int MyProperty { get; set; }
        }

        [DataContract]
        public class NonAttributedType
        {
            public int Foo { get; set; }
            public string Bar;
        }

        [DataContract]
        public class SimpleTypeFieldAttributed
        {
            [DataMember]
            public int MyField;
        }

        [DataContract]
        public class ComplexTypePropertyAttributed
        {
            [DataMember]
            public Foo Foo { get; set; }
        }

        [DataContract]
        private class ComplexTypeWithoutConverter
        {
            [DataMember]
            public ComplexTypeWithoutConverter Foo;
        }

        [TypeConverter(typeof(FooConverter))]
        public class Foo
        {
            public int Bar;
            public string Baz { get; set; }
        }

        [DataContract]
        public class PropertyTypeConverterSpecified
        {
            [DataMember]
            [TypeConverter(typeof(CustomBoolConverter))]
            public bool MyProperty { get; set; }
        }

        [DataContract]
        public class FieldTypeConverterSpecified
        {
            [DataMember]
            [TypeConverter(typeof(CustomBoolConverter))]
            public bool MyField;
        }

        public class CustomBoolConverter
            : TypeConverter
        {
            public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
            {
                bool f = (bool)value;
                return f ? "y" : "n";
            }

            public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
            {
                string s = value as string;
                return string.Equals("y", s, StringComparison.Ordinal);
            }
        }


        public class FooConverter
            : TypeConverter
        {
            public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
            {
                var f = value as Foo;
                return string.Concat(f.Bar, ":", f.Baz);
            }

            public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
            {
                var s = value as string;
                var values = s.Split(':');

                var result = new Foo();
                result.Bar = int.Parse(values[0]);
                result.Baz = values[1];

                return result;
            }
        }
    }
}